const MagicString = require("magic-string"); //rollup作者为了写rollup单独写的一个库
const {parse} = require('acorn');

const analyse = require("./ast/analyse");
const {has} = require('./utils');

const SYSTEM_VARIABLES = ['console','log'];

//每个文件都是一个模块
//每个模块都会对应一个Module实例
class Module {
  constructor({code,path,bundle}) {
    // Advanced: a source can include an `indentExclusionRanges` property
    // alongside `filename` and `content`. This will be passed to `s.indent()`
    this.code = new MagicString(code, {filename: path} /*←目前没撒用*/);

    this.path = path;
    this.bundle = bundle;

    this.ast = parse(code, {
      ecmaVersion: 7,
      sourceType: 'module'
    });

    this.canonicalNames = {};

    this.imports = {};
    this.exports = {};
    this.definitions = {}; //存放着所有的全局变量的定义语句
    this.modifications = {};
    //①↓ 会对 ↑ (this.imports、this.exports、this.definitions) 进行赋值
    //②给ast的每个statement（顶级语句）下挂载了 _source(该条语句对应的源码)、_defines(是否是顶级语句)、_dependsOn(是否依赖外置变量)等属性
    this.analyse();
  }

  analyse() {
    //填充 this.imports、 this.exports
    this.ast.body.forEach(node =>   {

      if(node.type === 'ImportDeclaration'){
        const source = node.source.value; //'./msg'
        const specifiers = node.specifiers; // [name,age] // 即 import {name, age} from './msg'; 的 `{name, age}`

        specifiers.forEach(specifier => {
          const name = specifier.imported.name; //name
          const localName = specifier.local.name; //name // import {name as MyName,age}from './msg', 因为可以将导入的重新as为一个变量 故这里除了 specifier.imported.name 外 还专门有一个 specifier.local.name // 查看 00.import.png

          //本地的哪个变量, 是从哪个模块的哪个变量导出的
          //---> this.imports.age = {name:'age',localName:'age',source:'./msg'}
          this.imports[localName] = {name, localName, source};
        });

      //导出有很多种, 更好的写法是 /^Export/.test(node.type)
      }else if (node.type === 'ExportNamedDeclaration') {
        const declaration = node.declaration; //VariableDeclaration

        if (declaration.type === 'VariableDeclaration') { // 查看 01.export.png
          const name = declaration.declarations[0].id.name; //export const name = 'ahhh'; 的 name 这个变量名
          //this.exports['name'] = {node,localName:name,expression}
          this.exports[name] = {
            node, localName: name, expression: declaration
          }
        }
      }
    });

    analyse(this.ast, this.code, this); //给ast的每个statement（顶级语句）下挂载了 _source、_defines、_dependsOn等属性

    //根据analyse出的_defines 填充this.definitions (全局变量的定义语句)
    this.ast.body.forEach(statement => {
      Object.keys(statement._defines).forEach(name => {
        //key是全局变量名, 值是定义这个全局变量的语句
        this.definitions[name] = statement;
      });

      Object.keys(statement._modifies).forEach(name => {
        if(!has(this.modifications,name)){
          this.modifications[name] = [];
        }
        this.modifications[name].push(statement); //对同一个变量可能会进行多次修改
      });
    });
  }

  //展开所有节点
  expandAllStatements(){
    const allStatements = [];
    this.ast.body.forEach(statement => {
      if(statement.type === 'ImportDeclaration') return; //打包后的最终代码里 import xxx 这样的语句都会被删除
      if(statement.type === 'VariableDeclaration') return; //靠其它地方展开它 （比如 `console.log(age)` 就会把 `var age = 12` 包含进来
      //todo 函数声明也是一样的 （我们这里直接在 02.mini-rollup/lib/bundle.js generate 做了简单的处理，更好的做法应该是在这里处理
      const statements = this.expandStatement(statement); //之所以叫展开语句 就是因为 一条语句 可能依赖了 外部变量 这个时候就需要把外部模块里对应这个变量的内容一并拿过来
      allStatements.push(...statements);
    });
    return allStatements;
  }

  //展开一个节点
  expandStatement(statement) {
    const result = [];
    statement._included = true;
    //↑ 必须放在 this.getDefinition 前 (防止死循环

    const dependencies = Object.keys(statement._dependsOn); // 该条语句 使用到的 外部变量 (有analyse.js analyse 分析得到的)
    dependencies.forEach(name => {
      //找到定义这个变量的声明节点, 这个节点可能在当前模块内, 也可能在依赖的模块里
      const definition = this.getDefinition(name);
      result.push(...definition);
    });

    result.push(statement);
    //↑ 必须放在 this.getDefinition 后 (因为分析出的依赖的语句要放在本语句前

    const defines = Object.keys(statement._defines);
    defines.forEach(name => {
      const modifications = has(this.modifications, name) && this.modifications[name];
      if(modifications){
        modifications.forEach(statement => {
          if (!statement._included) {
            const statements = this.expandStatement(statement);
            result.push(...statements);
          }
        });
      }
    });

    return result;
  }

  getDefinition(name){
    //查找一下导入变量里有没有name

    if(has(this.imports,name)){
      //this.imports.age = {name:'age',localName:'age',source:'./msg'}
      const importData = this.imports[name]; //没错 rollup 默认只支持es6module, 即只会解析import 不会解析 require

      //获取依赖的子模块
      const module = this.bundle.fetchModule(importData.source, this.path);

      //this.exports['name'] = {node,localName:name,expression}
      const exportData = module.exports[importData.name];

      return module.getDefinition(exportData.localName);

    //不是的话 就是本模块全局的
    }else {
      const statement = this.definitions[name];

      if(statement) {
        if(statement._included) return [];
        return this.expandStatement(statement); //export var name = 'ahhh' + xx
      }else if(SYSTEM_VARIABLES.includes(name)) {
        return [];
      }else {
        throw new Error(`变量${name}既没有从外部导入，也没有在当前的模块内声明`);
      }
    }
  }

  getCanonicalName(localName){
    //this.canonicalNames 存放着所有的命名的对应关系
    if(!has(this.canonicalNames,localName)){
      this.canonicalNames[localName] = localName; //默认值 key 和 value是一样的 也就是没有命名冲突的情况
    }

    return this.canonicalNames[localName];
  }

  //重命名
  rename(localName,replaceName){
    this.canonicalNames[localName] = replaceName;
  }
}

module.exports = Module;
